import ee from "event-emitter";
import { delay } from "./utils";
import log from "./log";
import { PLAY_MODE, PlayerStatus, NEXT_INDEX_INITIATOR, END_REASON } from '@/types/player'

const initialState = {
  startUrl: null,
  startIndex: null,
  endIndex: null,
  nextIndex: null,
  nextIndexInitiator: NEXT_INDEX_INITIATOR.INIT, // 下一个索引发起者
  errorIndex: null,
  doneIndices: [],
  mode: PLAY_MODE.STRAIGHT,
  resources: [],
  status: PlayerStatus.STOPPED,
};

// Note: A generic player for consuming some kind of resources
// It supports 3 modes: single, straight, loop.
// Also for straight and loop, it can start or end at any valid index you want
//
// The main API of a player is
// 1. constructor({ run: Function,  prepare: Function })
// 2. play(config)
// 3. pause()
// 4. resume()
// 5. stop()
//
// Events it emits
// 1. START
// 2. PREPARED
// 3. TO_PLAY
// 4. PLAYED_LIST
// 5. PAUSED
// 6. RESUMED
// 7. END
// 8. ERROR

export class Player {
  state: any = {
    ...initialState,
  };

  __prepare: (state: any) => void
  __beforeRun: (resource: any, state: any) => any
  __run: (resource: any, state: any) => any
  __handle: (res: any, resource: any, state: any) => Promise<number | void>
  toResumePromises: any = {};
  emit: any;
  on: any;

  constructor(opts: any, state?: any) {

    console.log(opts, 'opts')

    if (!opts) {
      throw new Error(
        "Player - constructor: must provide opts as 1st argument"
      );
    }

    if (typeof opts.prepare !== "function") {
      throw new Error("Player - constructor: must provide a prepare function");
    }

    if (typeof opts.beforeRun !== "function") {
      throw new Error("Player - constructor: must provide a beforeRun function");
    }

    if (typeof opts.run !== "function") {
      throw new Error("Player - constructor: must provide a run function");
    }

    if (typeof opts.handleResult !== "function") {
      throw new Error(
        "Player - constructor: must provide a handleResult function"
      );
    }

    this.__prepare = opts.prepare; // Player准备
    this.__beforeRun = opts.beforeRun; // 单条命令的准备
    this.__run = opts.run; // 执行单条命令
    this.__handle = opts.handleResult; // 处理单条命令的结果
    this.__setState(state || {}); // 设置状态
  }

  // 传入concig开始执行
  play(config: {
    mode: string;
    startUrl?: string;
    title: string;
    loopsStart?: number;
    loopsEnd?: number;
    resources: any[];
    startIndex?: number;
    nextIndex?: number;
    endIndex?: number;
    extra?: any;
    doneIndices?: string[];
    noEndEvent?: boolean;
    token?: string;
    isStep?: boolean;
    loopsCursor?: number;
    isBackFromCalling?: boolean;
    needDelayAfterLoop?: boolean;
    preDelay?: number;
    postDelay?: number;
    breakpoints?: number[];
    public?: any;
    callback?: (error: any, reason: any) => void
  }) {
    if (!config) {
      throw new Error("Player - play: config should not be empty");
    }

    if (!config.mode || !PLAY_MODE[config.mode as PLAY_MODE]) {
      // 校验mode
      throw new Error(
        "Player - play: must provide a valid mode, now it is " + config.mode
      );
    }


    // 如果为循环模式，校验循环播放的参数
    // 循环范围为loopsStart~loopsEnd
    // 使用循环游标标记当前的循环位置
    if (config.mode === PLAY_MODE.LOOP &&
      (!config.loopsStart ||
        config.loopsStart < 0 ||
        Math.floor(config.loopsStart) !== config.loopsStart ||
        !config.loopsEnd ||
        config.loopsEnd < config.loopsStart ||
        Math.floor(config.loopsEnd) !== config.loopsEnd)
    ) {
      throw new Error(
        `Player - play: must provide a valid tuple of "loopsStart" and "loopsEnd" in loop mode, now it is ${config.loopsStart}, ${config.loopsEnd}`
      );
    }

    if (config.resources.length !== 0) {
      // 校验startIndex的范围
      if (typeof config.startIndex !== 'number' || config.startIndex < 0 || config.startIndex >= config.resources.length) {
        throw new Error(
          `Player - play: startIndex out of range, now it is ${config.startIndex}, len: ${config.resources.length}`
        );
      }
    }

    // 如果提供了endIndex，校验endIndex的范围
    if (typeof config.endIndex === 'number' && (config.endIndex < 0 || config.endIndex >= config.resources.length)) {
      throw new Error(
        `Player - play: endIndex out of range, now it is ${config.endIndex}, len: ${config.resources.length}`
      );
    }

    const {
      nextIndex, // 下一个
      startIndex, // 开始
      startUrl, // 开始url
      resources, // 资源
      title, // 名称
      extra, // 额外数据
      doneIndices, // 已经完成的
      noEndEvent, // 不发送结束事件
      token, // 密钥
      isStep, // 是否单步运行
      loopsCursor, // 当前循环次数
      loopsStart, // 循环开始
      loopsEnd, // 循环结束
      isBackFromCalling, // 是否
      needDelayAfterLoop, // 循环结束是否需要等待
    } = config;

    const endIndex = config.endIndex || resources.length - 1;

    const basicState: any = {
      token,
      title,
      extra,
      needDelayAfterLoop,
      isBackFromCalling,
      startUrl,
      startIndex,
      endIndex,
      nextIndex: nextIndex !== undefined ? nextIndex : startIndex,
      errorIndex: null,
      doneIndices: doneIndices || [],
      mode: config.mode,
      loopsCursor: 1,
      loopsStart: 1,
      loopsEnd: 1,
      isStep: isStep || false,
      noEndEvent: noEndEvent || false,
      resources: config.resources,
      breakpoints: config.breakpoints || [],
      status: PlayerStatus.PLAYING,
      public: config.public || {},
      callback: config.callback || function () { },
      lastPlayConfig: config,
      playUID: Math.random(),
    };

    if (typeof config.preDelay === 'number') {
      basicState.preDelay = config.preDelay
    }

    if (typeof config.postDelay === 'number') {
      basicState.postDelay = config.postDelay
    }

    switch (config.mode) {
      // 顺序执行
      case PLAY_MODE.STRAIGHT: {
        this.__setState({
          ...basicState,
        });
        break;
      }
      // 执行单行命令
      case PLAY_MODE.SINGLE: {
        this.__setState({
          ...basicState,
          endIndex: startIndex,
        });
        break;
      }
      // 循环执行
      case PLAY_MODE.LOOP: {
        this.__setState({
          ...basicState,
          loopsStart,
          loopsEnd,
          loopsCursor: loopsCursor !== undefined ? loopsCursor : loopsStart,
        });
        break;
      }
      default: {
        break;
      }
    }

    // 开始
    this.emit("START", {
      title,
      loopsCursor: this.state.loopsCursor,
      doneIndices: this.state.doneIndices,
      extra: this.state.extra,
      isBackFromCalling: this.state.isBackFromCalling,
    });

    return Promise.resolve()
      .then(() => this.__prepare(this.state))
      .then(() => {
        // 准备就绪
        this.emit("PREPARED", {
          title,
          loopsCursor: this.state.loopsCursor,
          doneIndices: this.state.doneIndices,
          extra: this.state.extra,
          isBackFromCalling: this.state.isBackFromCalling,
        });
      })
      .then(
        () => this.__go(this.state.token || null),
        (e) => this.__errLog(e, e.errorIndex)
      );
  }

  // 暂停执行
  pause() {
    this.__setState({
      status: PlayerStatus.Paused,
    });

    setTimeout(() => {
      this.emit("PAUSED", { extra: this.state.extra });
    }, 0);

    return this.__createPromiseWaitForResume(this.state.token);
  }

  // 继续执行
  resume(isStep?: any) {
    this.__setState({
      status: PlayerStatus.PLAYING,
      isStep: !!isStep,
    });

    this.emit("RESUMED", { extra: this.state.extra });

    const item = this.toResumePromises[this.state.token];

    if (item && item.resolve) {
      item.resolve();
    }
  }

  // 终止
  stop(opts?: any) {
    console.log('stop')
    this.__end(END_REASON.MANUAL, opts);
  }

  // 终止并报错
  stopWithError(error: any) {
    this.__errLog(error);
  }

  // 跳转到指定index
  jumpTo(nextIndex: any) {
    const { resources } = this.state;

    // Note: validate nextIndex by resources.length instead of startIndex and endIndex,
    // to make it possible for 'run from here' to jump to commands ahead of the start point
    if (nextIndex < 0 || nextIndex >= resources.length) {
      throw new Error("jumpTo: nextIndex out of range");
    }

    this.__setState({
      nextIndex,
    });
  }

  // 设置postDelay
  setPostDelay(n: any) {
    this.__setState({
      postDelay: n,
    });
  }

  // 设置superFast
  setSuperFastMode(val: any) {
    this.__setState({
      superFast: val,
    });
  }

  // 获取当前状态
  getStatus() {
    return this.state.status;
  }

  // 获取数据
  getState() {
    return { ...this.state };
  }

  // 设置数据
  setState(state: any) {
    return this.__setState(state);
  }

  // 以最后一次的配置重新执行
  replayLastConfig() {
    const config = this.state.lastPlayConfig;
    if (!config) throw new Error("No last play config available");

    return this.play({
      ...config,
      nextIndex: config.startIndex,
    });
  }

  // Note: playUID changes on every `play` call
  // it's useful for features with timer to tell if it should continue to run
  getPlayUID() {
    return this.state.playUID;
  }

  checkPlayUID(uid: any) {
    return this.state.playUID === uid;
  }

  __go(token: any) {

    // 使用token，确保顺序 Note: in case it is returned from previous call
    if (!token) {
      this.state.token = token = Math.random();
    } else if (token !== this.state.token) {
      return;
    }

    const guardToken = (fn: any) => (...args: any[]) => {
      if (token !== this.state.token) {
        throw new Error("token expired");
      }
      return fn(...args);
    };

    const { preDelay } = this.state;
    const pre = preDelay > 0 ? this.__delay(() => undefined, preDelay) : Promise.resolve();

    // Note: the flow of this process:
    // 1. delay if `preDelay` set 如果preDelay设置了，等待
    // 2. check `__shouldContinue`
    // 3. stop if the player is stopped or paused
    // 4. otherwise call `__run` to actually consume the current resource
    // 5. set the state to next by calling `__setNext`
    // 6. delay if `postDelay` set
    return pre
      .then(() => {
        return this.__shouldContinue();
      })
      .then(({ paused, complete }: any) => {
        if (paused) {
          throw new Error("player: paused or stopped");
        }
        if (complete) {
          return;
        }
        const {
          resources,
          nextIndex,
          startIndex,
          loopsCursor,
          loopsStart,
          loopsEnd,
          nextIndexInitiator,
        } = this.state;

        const obj = {
          loopsCursor,
          index: nextIndex,
          currentLoop: loopsCursor - loopsStart + 1,
          loops: loopsEnd - loopsStart + 1,
          resource: resources[nextIndex],
          extra: this.state.extra,
        };

        // Note: when we're running loops
        const isBottomFrame = !this.state.extra || this.state.extra.isBottomFrame;

        if (isBottomFrame && nextIndex === startIndex) {
          if (nextIndexInitiator === NEXT_INDEX_INITIATOR.LOOP || nextIndexInitiator === NEXT_INDEX_INITIATOR.INIT) {
            // 第一次循环
            this.emit("LOOP_START", obj);
          }
          if (nextIndexInitiator === NEXT_INDEX_INITIATOR.LOOP && loopsCursor !== loopsStart) {
            // 又一次循环开始
            this.emit("LOOP_RESTART", obj);
          }
        }

        // 回放
        this.emit("TO_PLAY", {
          index: nextIndex,
          currentLoop: loopsCursor - loopsStart + 1,
          loops: loopsEnd - loopsStart + 1,
          resource: resources[nextIndex],
          extra: this.state.extra,
        });

        // 断点
        const hasBreakpoints = this.state.breakpoints?.length > 0;

        // **Info: breakpoint promise takes 30ms
        const possibleBreakpointPromise = !hasBreakpoints
          ? Promise.resolve()
          : (() => {
            log("8. possibleBreakpointPromise:>> ");
            // Note: there will never be two breakpoints in straight. Use `lastBreakpoint` to tell whether we just hit a breakpoint
            // Also note that, 'TO_PLAY' events need to be fired before we pause.
            if (this.state.lastBreakpoint === undefined && this.state.breakpoints.indexOf(nextIndex) !== -1) {
              this.__setState({ lastBreakpoint: nextIndex });
              // 遇到断点，暂停
              this.emit("BREAKPOINT", {
                index: nextIndex,
                currentLoop: loopsCursor - loopsStart + 1,
                loops: loopsEnd - loopsStart + 1,
                resource: resources[nextIndex],
                extra: this.state.extra,
              });
              return this.pause();
            } else {
              // 没有断点继续
              this.__setState({ lastBreakpoint: undefined });
              return Promise.resolve();
            }
          })();

        // Note: Check whether token expired or not after each async operations
        // Also also in the final catch to prevent unnecessary invoke of __errLog
        return (
          possibleBreakpointPromise
            // ** This is where player run happens
            .then(() => this.__beforeRun(resources[nextIndex], this.state))
            .then(({ command, state }) => this.__run(command, state)) // 执行命令
            .then(guardToken((res: any) => {
              // Note: allow users to handle the result
              return this.__handle(res, resources[nextIndex], this.state) // 处理结果并返回nextIndex
                .then(guardToken((nextIndex?: number) => {
                  // Note: __handle has the chance to return a `nextIndex`, mostly when it's
                  // from a flow logic. But still, it could be undefined for normal commands
                  const oldLoopsCursor = this.state.loopsCursor;
                  this.__setNext(nextIndex);
                  // TODO: re-consider this. It delays the chain by 20ms for this go to the next then statement
                  // if( !superFast ) {
                  // ** this is important for played command to turn green
                  this.emit("PLAYED_LIST", {
                    indices: this.state.doneIndices,
                    extra: this.state.extra,
                  });
                  // }
                  return oldLoopsCursor !== this.state.loopsCursor;
                }))
                .then((isLoopsCursorChanged: any) => {
                  // 执行后延时，__handle可能改变了postDelay
                  const { postDelay, needDelayAfterLoop } = this.state;
                  const delay = Math.max(postDelay, isLoopsCursorChanged && needDelayAfterLoop ? 10 : 0);
                  return delay > 0 ? this.__delay(() => undefined, delay) : Promise.resolve();
                })
                .then(() => {
                  // 单步执行，暂停直到继续,非单步直接执行
                  return this.state.isStep ? this.pause().then(() => this.__go(token)) : this.__go(token);
                });
            }))
            .catch(guardToken((err: any) => this.__errLog(err)))
        );
      });
  }

  // 是否继续
  __shouldContinue() {
    const { status, nextIndex, startIndex, endIndex } = this.state;
    if (status === PlayerStatus.Paused || status === PlayerStatus.STOPPED) {
      // 状态为暂停或停止
      // Note: when it's paused, use a pending promise to holde the execution
      // so we can continue running after resume and resolve the promise
      const promiseItem = this.toResumePromises[this.state.token];
      return promiseItem ? promiseItem.promise.then(() => ({})) : { paused: true };
    } else if (status === PlayerStatus.PLAYING && nextIndex >= startIndex && nextIndex <= endIndex) {
      // 状态为执行中，并且nextIndex在范围内
      return Promise.resolve({ paused: false, complete: false });
    } else {
      // 执行完成了
      this.__end(END_REASON.COMPLETE, {});
      return Promise.resolve({ complete: true });
    }
  }

  // 用于暂停恢复的Promise
  __createPromiseWaitForResume(token: any) {
    const p = new Promise((resolve, reject) => {
      setTimeout(() => {
        this.toResumePromises[token] = {
          resolve,
          reject,
          promise: p,
        };
      }, 10);
    });
    return p;
  }

  // 用于停止的Promise
  __createPromiseForStop(token: any, stopReason: any) {
    const p = new Promise((resolve, reject) => {
      setTimeout(() => {
        this.toResumePromises[token] = {
          resolve,
          reject,
          promise: p,
        };
        reject(new Error(`Stop reason: ${stopReason}`));
      }, 10);
    });
    return p;
  }

  // 停止
  __end(reason: any, opts: any) {

    // Note: CANNOT end the player twice
    if (this.state.status === PlayerStatus.STOPPED) return;

    if (Object.keys(END_REASON).indexOf(reason) === -1) {
      // 校验失败原因
      throw new Error("Player - __end: invalid reason, " + reason);
    }

    const silent = opts && opts.silent;
    const noEndEvent = this.state.noEndEvent && reason === END_REASON.COMPLETE;

    if (!noEndEvent && !silent) {
      this.emit("END", { opts, reason, extra: this.state.extra });

      if (reason !== END_REASON.ERROR) {
        // 回调失败原因
        this.state.callback(null, reason);
      }
    }

    // 不是正常结束
    if (reason !== END_REASON.COMPLETE) {
      this.__createPromiseForStop(this.state.token, reason);
    }

    this.__setState({
      status: PlayerStatus.STOPPED,
    });

    if (this.state.extra && this.state.extra.isBottomFrame) {
      this.__setState({
        nextIndexInitiator: NEXT_INDEX_INITIATOR.INIT,
      });
    }
  }

  __errLog(err: any, errorIndex?: any) {

    console.log(err, errorIndex, 'error')

    // Note: CANNOT log error if player is already stopped
    if (this.state.status === PlayerStatus.STOPPED) {
      throw new Error(err);
    }

    this.emit("ERROR", {
      errorIndex: errorIndex !== undefined ? errorIndex : this.state.nextIndex,
      msg: err && err.message,
      stack: err && err.stack,
      extra: this.state.extra,
      restart: !!err.restart,
    });
    this.state.callback(err, null);
    this.__end(END_REASON.ERROR, {
      msg: err && err.message,
      stack: err && err.stack,
    });

    throw new Error(err);
  }

  __setNext(nextIndexPassed: any) {
    if (typeof nextIndexPassed === 'number' && (nextIndexPassed < 0 || nextIndexPassed > this.state.resources.length)) {
      // 是否超出范围
      throw new Error(`invalid nextIndexPassed ${nextIndexPassed}`);
    }

    const {
      mode,
      doneIndices,
      nextIndex,
      endIndex,
      startIndex,
      loopsCursor,
      loopsEnd,
    } = this.state;

    const nextIndexToSet = nextIndexPassed !== undefined ? nextIndexPassed : nextIndex + 1;

    let done = doneIndices.indexOf(nextIndex) === -1 ? [...doneIndices, nextIndex] : doneIndices;

    let lcur = loopsCursor; // 循环光标
    let next = null;
    let initiator = NEXT_INDEX_INITIATOR.NORMAL;

    if (mode === PLAY_MODE.LOOP) {
      // 循环模式
      if (nextIndexToSet <= endIndex) {
        // 本次循环没完成
        next = nextIndexToSet;
      } else if (loopsCursor >= loopsEnd) {
        // 最后一次循环了
        next = nextIndexToSet;
      } else {
        // 进入下一次循环，循环游标+1
        lcur += 1;
        next = startIndex;
        done = [];
        initiator = NEXT_INDEX_INITIATOR.LOOP;
      }
    } else {
      // 非循环模式
      next = nextIndexToSet;
    }

    // __setNext is still called after __end
    // so to protect the INIT value, check whether
    // it's already stopped
    if (this.state.status === PlayerStatus.STOPPED && this.state.nextIndexInitiator === NEXT_INDEX_INITIATOR.INIT) {
      initiator = NEXT_INDEX_INITIATOR.INIT;
    }

    this.__setState({
      loopsCursor: lcur,
      nextIndex: next,
      nextIndexInitiator: initiator,
      doneIndices: done,
    });
  }

  __setState(obj: any) {
    this.state = {
      ...this.state,
      ...obj,
    };
  }

  __delay(fn: any, timeout: any) {
    let past = 0;
    const timer = setInterval(() => {
      past += 1000;
      this.emit("DELAY", {
        extra: this.state.extra,
        total: timeout,
        past,
      });
    }, 1000);

    return delay(fn, timeout).then((res: any) => {
      if (timer) clearInterval(timer);
      return res;
    });
  }
}

ee(Player.prototype);